/**
 * Zanzibar - A regexp engine on UIMA Annotation
 * Copyright (C) 2011
 * author: Bastianelli Emanuele <emanuele.bastianelli@gmail.com>
 * author: Caponi Emiliano      <emiliano.caponi@gmai.com>
 * author: Castellucci Giuseppe <castellucci.giuseppe@gmail.com>
 * author: Alessandro Rossi     <berardino.viola@gmai.com>
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package ia1011.zanzibar.processor;

import ia1011.zanzibar.featurestructures.FeatureStructure;
import ia1011.zanzibar.processor.fsm.FSM;
import ia1011.zanzibar.processor.fsm.State;
import ia1011.zanzibar.processor.fsm.Transition;
import ia1011.zanzibar.rules.Rule;
import ia1011.zanzibar.rules.newParser.RulesParser;

import java.util.*;
import java.io.*;

import org.apache.uima.cas.FSIterator;
import org.apache.uima.jcas.JCas;
import org.apache.uima.jcas.tcas.Annotation;

/**
 * Class that represents the aggregation engine.
 * 
 * @author Alessandro Perrino (creator)
 * @author <br> Bastianelli E. emanuele.bastianelli@gmail.com (modifier)
 * @author <br> Caponi E. emiliano.caponi@gmail.com (modifier)
 * @author <br> Castellucci G. castellucci.giuseppe@gmail.com (modifier)
 * @author <br> Rossi A. berardino.viola@gmail.com (modifier)
 *  
 */
public class RegExAggregator {
	FSM fsm;
	private JCas jcas;
	@SuppressWarnings("unused")
	private ArrayList<Annotation> sequence;
	private ArrayList<Annotation> matched;
	private String indexName;
	
	/**
	 * Constructor
	 * 
	 * @param jcas Jcas
	 */
	public RegExAggregator(JCas jcas) {		
		this.jcas = jcas;
	}
	
	/**
	 * Constructor
	 * 
	 * @param jcas Jcas
	 * @param indexName String: represents the index name
	 */
	public RegExAggregator(JCas jcas, String indexName) {		
		this.jcas = jcas;
		this.indexName = indexName;		
	}

	/**
	 * Initializes the FSM starting from an array of rules 
	 * 
	 * @param r Rule[] an array of Rule object used to initialize the FSM
	 */
	public void initialize(Rule[] r) {
		try {
			fsm = new FSM(r);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/** 
	 * Initializes the FSM reading the rule set from a zRule file
	 * 
	 *  @param ruleFilePath String
	 */
	@SuppressWarnings("static-access")
	public void initialize(String ruleFilePath) {
		RulesParser parser = null;
        try {
        	File ruleFile = new File(ruleFilePath);        	
        	parser = new RulesParser(new FileReader(ruleFile));
        	parser.Start();
        	Rule[] rules = parser.toRuleArray();
        	this.initialize(rules);        	
        } catch (Exception e) {
        	e.printStackTrace();
        }
	}
	
	/**
	 *  Method used to start aggregation process based on regex on Feature
	 *  Structure. Require initialization of RegExAggregator with a zRule
	 *  file and a index,.
	 *  
	 * @return JCas
	 */
	public JCas run() {
		sequence = new ArrayList<Annotation>();				
		if (indexName == null) { //use default index
			FSIterator<Annotation> iterator = 
				jcas.getAnnotationIndex().iterator();
			int remaining = jcas.getAnnotationIndex().size();			
			while (iterator.hasNext()) {
				matched = new ArrayList<Annotation>();
				remaining = applyFirstOnDefaultIndex(iterator, remaining);
			}
			return jcas;
		} else { // use the specified index
			FSIterator<org.apache.uima.cas.FeatureStructure> iterator = 
				jcas.getFSIndexRepository().getIndex(indexName).iterator();
			int remaining =  jcas.getFSIndexRepository().getIndex(indexName).size();
			while (iterator.hasNext()) {
				matched = new ArrayList<Annotation>();
				remaining = applyFirstOnUserIndex(iterator, remaining);
			}
			return jcas;	
		}		
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	private int applyFirstOnUserIndex
		(FSIterator<org.apache.uima.cas.FeatureStructure> indexIter,
				int remaining) {
		FSIterator<org.apache.uima.cas.FeatureStructure> tmpIndexIter =
			indexIter.copy();
		int k = 0;
		Map currentVariables = new HashMap();
		Map finalVariables = null;
		Vector bindings = new Vector();
		Vector activeStates = new Vector();
		State finalState = null;
		int length = 0;
		State currentState = this.fsm.getInitial();
		activeStates.add(currentState);
		bindings.add(currentVariables);
		while (!activeStates.isEmpty()) {
			int size = activeStates.size();
			for (int i = 0; i < size; ++i) {
				currentState = (State) activeStates.firstElement();
				currentVariables = (Map) ((HashMap) bindings.firstElement())
						.clone();
				if (currentState.isFinal()) {
					if ((finalState == null)
							|| (finalState.getRule().getWeight() <= 
								currentState.getRule().getWeight())) {
						finalState = currentState;
						finalVariables = (Map) ((HashMap) currentVariables)
								.clone();
						length = k;
					}
				}
				Iterator iter = currentState.getTransitions().iterator();
				while (iter.hasNext()) {
					Transition currentTransition = (Transition) iter.next();					
					FeatureStructure constraint = (FeatureStructure) 
						currentTransition.getConstraint().clone();
					System.out.println("Feature Structure Constraint :");
					constraint.display();
					constraint.fillEmptyVariables(currentVariables);
					try {
						if (k < remaining) {							
							if (constraint.subsumption(
									(Object) tmpIndexIter.get())) {
								activeStates.add(currentTransition.getTarget());
								constraint.saveVariables(currentVariables);
								bindings.add(currentVariables);								
							}
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
					currentVariables = (Map) ((HashMap) bindings.firstElement())
							.clone();
				}
				activeStates.remove(0);
				bindings.remove(0);
			}
			k++;
			tmpIndexIter.moveToNext();
		}		
		if (finalState != null) {
			FeatureStructure rhs = (FeatureStructure) finalState.getRule()
					.getRhs().clone();
			rhs.fillEmptyVariables(finalVariables);			
			for (int i = 0; i<length; ++i) {
				Annotation annot = (Annotation)indexIter.next(); 
				matched.add(annot);
				remaining = remaining - 1;				
			}
			Object toCreateObject = rhs.toObject(jcas, matched);
			Annotation toCreate = (Annotation) toCreateObject; 
			toCreate.setBegin(matched.get(0).getBegin());
			toCreate.setEnd(matched.get(length-1).getEnd());
			toCreate.addToIndexes();
			return remaining;
		} else {
			indexIter.moveToNext();
			remaining = remaining-1;						
			return remaining;		
		}
	}
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	private int applyFirstOnDefaultIndex(FSIterator<Annotation> indexIter,
			int remaining) {
		FSIterator<Annotation> tmpIndexIter =
			indexIter.copy();		
		int k = 0;
		Map currentVariables = new HashMap();
		Map finalVariables = null;
		Vector bindings = new Vector();
		Vector activeStates = new Vector();
		State finalState = null;
		int length = 0;
		State currentState = this.fsm.getInitial();
		activeStates.add(currentState);
		bindings.add(currentVariables);
		while (!activeStates.isEmpty()) {
			int size = activeStates.size();
			for (int i = 0; i < size; ++i) {
				currentState = (State) activeStates.firstElement();
				currentVariables = (Map) ((HashMap) bindings.firstElement())
						.clone();
				if (currentState.isFinal()) {
					if ((finalState == null)
							|| (finalState.getRule().getWeight() <= 
								currentState.getRule().getWeight())) {
						finalState = currentState;
						finalVariables = (Map) ((HashMap) currentVariables)
								.clone();
						length = k;
					}
				}
				Iterator iter = currentState.getTransitions().iterator();
				while (iter.hasNext()) {
					Transition currentTransition = (Transition) iter.next();					
					FeatureStructure constraint = (FeatureStructure) 
						currentTransition.getConstraint().clone();
					constraint.display();
					constraint.fillEmptyVariables(currentVariables);
					try {
						if (k < remaining) {							
							if (constraint.subsumption(
									(Object)tmpIndexIter.get())) {
								activeStates.add(currentTransition.getTarget());
								constraint.saveVariables(currentVariables);
								bindings.add(currentVariables);								
							}
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
					currentVariables = (Map) ((HashMap) bindings.firstElement())
							.clone();
				}
				activeStates.remove(0);
				bindings.remove(0);
			}
			k++;
			tmpIndexIter.moveToNext();
		}		
		if (finalState != null) {
			FeatureStructure rhs = (FeatureStructure) finalState.getRule()
					.getRhs().clone();
			rhs.fillEmptyVariables(finalVariables);			
			for (int i = 0; i<length; ++i) {
				Annotation annot = indexIter.next(); 
				matched.add(annot);
				remaining = remaining -1;				
			}
			Object toCreateObject = rhs.toObject(jcas, matched);
			Annotation toCreate = (Annotation) toCreateObject; 
			toCreate.setBegin(matched.get(0).getBegin());
			toCreate.setEnd(matched.get(length-1).getEnd());
			toCreate.addToIndexes();
			return remaining;
		} else {
			indexIter.moveToNext();
			remaining = remaining-1;						
			return remaining;		
		}
	}
}
